
<!--generated with sswg-->
<style>
    html {max-width: 100%; margin: auto; color: #333333;}
    a.button {padding: 15px 32px; background-color: #555; border-radius: 2em; border-width: 0px; text-decoration: none; color: white; font-size: 25.0px; line-height: 2.5em;}
    a.button:hover {background-color: #777}
    a.button_big {padding: 0.5em; background-image: linear-gradient(to top, #427b0e, #9ba97d); background-color: lightgray; background-blend-mode: multiply; border-radius: .75em; border-width: 0px; text-decoration: none; min-width: 150px; max-width: 150px; min-height: 150px; max-height: 150px; display: inline-block; vertical-align: top; margin: 4px 4px 10px 4px; color: white; font-size: 25.0px; background-size: auto 100%; background-position-x: center;}
    a.button_big:hover {background-color: white; color: #e6d23f; text-decoration: underline;}
    mark {background: #ccff99;}
    span {background-color: rgba(0, 0, 0, 0.55); padding: .1em; line-height: 1.35em;}
    img {max-width: 100%; vertical-align: top;}
    .code_block {background-color: whitesmoke; padding: 10px; margin: 0; font-family: monospace; font-size: 20; font-weight: normal; white-space: pre;}

</style>
<html>
<left>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>inventory_tutorial</title>
<br>

<div style="text-align: center;max-width: 1000px; margin: auto;font-family: arial;">
<div style="font-size: 100.0px;font-weight: bold;">
Inventory Tutorial<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
In this tutorial we will make a drag and drop inventory like this.<br>
<br>
<img src="inventory_2.gif"></img> <br>
<br>
<br>
<br>
<div style="text-align: left;">
<div style="font-size: 40.0px;font-weight: bold;">
<div id="1 Setting up the class"/><br>
1 Setting up the class<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's begin by making an Inventory class<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><font color="purple">from</font> ursina <font color="purple">import</font> *<br><br>class Inventory(<font color="olive">Entity</font>):<br>    <font color="purple">def</font> __init__(self):<br>        super().__init__()<br><br><br><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="2 Adding graphics"/><br>
2 Adding graphics<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;However, if we run the code, we'll see that there's nothing visible.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's parent it to the ui and set the model to 'quad', an included model.<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><font color="purple">from</font> ursina <font color="purple">import</font> *<br><br>class Inventory(<font color="olive">Entity</font>):<br>    <font color="purple">def</font> __init__(self):<br>        super().__init__(<br><mark>            parent = camera.ui,                                         </mark><br><mark>            model = <font color="green">'quad'</font>                                              </mark><br>            )<br><br><br><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;If we look at it now, we see it's a white square in the middle of the screen.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's set it to a nicer shape. We also want (0,0) to be in the upper left corner<br>
&nbsp;&nbsp;&nbsp;&nbsp;because it makes it easier to add items later. Let's also give it a texture and a color.<br>
&nbsp;&nbsp;&nbsp;&nbsp;If we have both, the color value will tint the texture.<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Now, this is nice and all, but wouldn't it be nice to show a grid as well?<br>
&nbsp;&nbsp;&nbsp;&nbsp;There are multiple way to do that, but in this case, we'll simply make the texture repeat<br>
&nbsp;&nbsp;&nbsp;&nbsp;by setting texture_scale to (5,6). That'll nicely fit the size of our inventory.<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><font color="purple">from</font> ursina <font color="purple">import</font> *<br><br>class Inventory(<font color="olive">Entity</font>):<br>    <font color="purple">def</font> __init__(self):<br>        super().__init__(<br>            parent = camera.ui,<br>            model = <font color="green">'quad'</font>,<br><mark>            scale = (.5, .8),                                           </mark><br><mark>            origin = (-.5, .5),                                         </mark><br><mark>            position = (-.3,.4),                                        </mark><br><mark>            texture = <font color="green">'white_cube'</font>,                                     </mark><br><mark>            texture_scale = (5,8),                                      </mark><br><mark>            color = color.dark_gray                                     </mark><br>            )<br><br><br><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="3 Adding placeholder items"/><br>
3 Adding placeholder items<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's try to add some items to the inventory.<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><font color="purple">from</font> ursina <font color="purple">import</font> *<br><br>class Inventory(<font color="olive">Entity</font>):<br>    <font color="purple">def</font> __init__(self):<br>        super().__init__(<br>            parent = camera.ui,<br>            model = <font color="green">'quad'</font>,<br>            scale = (.5, .8),<br>            origin = (-.5, .5),<br>            position = (-.3,.4),<br>            texture = <font color="green">'white_cube'</font>,<br>            texture_scale = (5,8),<br>            color = color.dark_gray<br>            )<br><br><br><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br><mark>    item = Button(parent=inventory, color=color.red, position=(0,0))            </mark><br><mark>    item = Button(parent=inventory, color=color.green, position=(2,0))          </mark><br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Well, that didn't go as planned. The items cover the entire inventory and<br>
&nbsp;&nbsp;&nbsp;&nbsp;the second item is way off to the left.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's fix this by making another object to put the items under.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Scale the object to the size of an item.<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;They also don't fit the grid. Fix that by setting origin to the upper left<br>
&nbsp;&nbsp;&nbsp;&nbsp;corner, (-.5,.5).<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><font color="purple">from</font> ursina <font color="purple">import</font> *<br><br>class Inventory(<font color="olive">Entity</font>):<br>    <font color="purple">def</font> __init__(self):<br>        super().__init__(<br>            parent = camera.ui,<br>            model = <font color="green">'quad'</font>,<br>            scale = (.5, .8),<br>            origin = (-.5, .5),<br>            position = (-.3,.4),<br>            texture = <font color="green">'white_cube'</font>,<br>            texture_scale = (5,8),<br>            color = color.dark_gray<br>            )<br><br><mark>        self.item_parent = <font color="olive">Entity</font>(parent=self, scale=(1/5,1/8))         </mark><br><br><br><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br><mark>    item = Button(parent=inventory.item_parent, origin=(-.5,.5), color=color.red, position=(0,0))      </mark><br><mark>    item = Button(parent=inventory.item_parent, origin=(-.5,.5), color=color.green, position=(2,0))    </mark><br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="4 Adding items"/><br>
4 Adding items<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;We've added some items manually to make sure they get the right scale and position,<br>
&nbsp;&nbsp;&nbsp;&nbsp;but we should make an append() function so it's easy to add items.<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's start by making a function called 'append()' and make it spawn an item<br>
&nbsp;&nbsp;&nbsp;&nbsp;when we send it a string. We'll also set the button's text to the string we<br>
&nbsp;&nbsp;&nbsp;&nbsp;receive so we can differentiate them.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's give them a random color too, why not.<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Lastly, let's call inventory.append('test item') a couple of times to make sure it works.<br>
<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><font color="purple">from</font> ursina <font color="purple">import</font> *<br><br>class Inventory(<font color="olive">Entity</font>):<br>    <font color="purple">def</font> __init__(self):<br>        super().__init__(<br>            parent = camera.ui,<br>            model = <font color="green">'quad'</font>,<br>            scale = (.5, .8),<br>            origin = (-.5, .5),<br>            position = (-.3,.4),<br>            texture = <font color="green">'white_cube'</font>,<br>            texture_scale = (5,8),<br>            color = color.dark_gray<br>            )<br><br>        self.item_parent = <font color="olive">Entity</font>(parent=self, scale=(1/5,1/8))<br><br><br><mark>    <font color="purple">def</font> append(self, item):                                             </mark><br><mark>        Button(                                                         </mark><br><mark>            parent = inventory.item_parent,                             </mark><br><mark>            model = <font color="green">'quad'</font>,                                             </mark><br><mark>            origin = (-.5,.5),                                          </mark><br><mark>            color = color.random_color(),                               </mark><br><mark>            z = -.1                                                     </mark><br>            )<br><br><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br><mark>    inventory.append(<font color="green">'test item'</font>)                                       </mark><br><mark>    inventory.append(<font color="green">'test item'</font>)                                       </mark><br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="5 Find a free slot in the inventory"/><br>
5 Find a free slot in the inventory<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;The items gets added, but they overlap. We need to find the first open slot in the inventory<br>
&nbsp;&nbsp;&nbsp;&nbsp;and place the item there. We can to this by checking each grid position and see if any<br>
&nbsp;&nbsp;&nbsp;&nbsp;if the items occupy that position already.<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><font color="purple">from</font> ursina <font color="purple">import</font> *<br><br>class Inventory(<font color="olive">Entity</font>):<br>    <font color="purple">def</font> __init__(self):<br>        super().__init__(<br>            parent = camera.ui,<br>            model = <font color="green">'quad'</font>,<br>            scale = (.5, .8),<br>            origin = (-.5, .5),<br>            position = (-.3,.4),<br>            texture = <font color="green">'white_cube'</font>,<br>            texture_scale = (5,8),<br>            color = color.dark_gray<br>            )<br><br>        self.item_parent = <font color="olive">Entity</font>(parent=self, scale=(1/5,1/8))<br><br><br><mark>    <font color="purple">def</font> find_free_spot(self):                                                      </mark><br><mark>        taken_spots = [(int(e.x), int(e.y)) for e in self.item_parent.children]    </mark><br><mark>        for y in range(8):                                                         </mark><br><mark>            for x in range(5):                                                     </mark><br><mark>                if not (x,-y) in taken_spots:                                      </mark><br><mark>                    return (x,-y)                                                  </mark><br><br><br>    <font color="purple">def</font> append(self, item):<br>        Button(<br>            parent = inventory.item_parent,<br>            model = <font color="green">'quad'</font>,<br>            origin = (-.5,.5),<br>            color = color.random_color(),<br><mark>            position = self.find_free_spot(),                                       </mark><br>            z = -.1<br>            )<br><br><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br><mark>    for i in range(7):                                                  </mark><br><mark>        inventory.append(<font color="green">'test item'</font>)                                   </mark><br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="6 Add random item button"/><br>
6 Add random item button<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Make a button to add a random item to the inventory. This is not part of the inventory itself,<br>
&nbsp;&nbsp;&nbsp;&nbsp;but it's useful in order to test it.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Assign the button's 'on_click' to a function, and it will call that function when we click it.<br>
&nbsp;&nbsp;&nbsp;&nbsp;button.on_click = inventory<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's make an tuple with those and make the button choose a random item from the tuple<br>
&nbsp;&nbsp;&nbsp;&nbsp;using random.choice(items)<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;"><br>if __name__ == <font color="green">'__main__'</font>:<br>    app = Ursina()<br>    inventory = Inventory()<br><br><mark>    <font color="purple">def</font> add_item():                                                                  </mark><br><mark>        inventory.append(random.choice((<font color="green">'bag'</font>, <font color="green">'bow_arrow'</font>, <font color="green">'gem'</font>, <font color="green">'orb'</font>, <font color="green">'sword'</font>))) </mark><br><br><mark>    for i in range(7):                                                  </mark><br><mark>        add_item()                                                      </mark><br><br><mark>    add_item_button = Button(                                           </mark><br><mark>        scale = (.1,.1),                                                </mark><br><mark>        x = -.5,                                                        </mark><br><mark>        color = color.lime.tint(-.25),                                  </mark><br><mark>        text = <font color="green">'+'</font>,                                                     </mark><br><mark>        tooltip = Tooltip(<font color="green">'Add random item'</font>),                           </mark><br><mark>        on_click = add_item                                             </mark><br><mark>        )                                                               </mark><br><br>    app.run()<br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="7 Adding textures and hover text"/><br>
7 Adding textures and hover text<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;To add textures, just set texture=item and it will try to find a<br>
&nbsp;&nbsp;&nbsp;&nbsp;texture with that name. It searches first in the project's assets and then<br>
&nbsp;&nbsp;&nbsp;&nbsp;in the assets included with ursina. I've included some icons for the purpose<br>
&nbsp;&nbsp;&nbsp;&nbsp;if this tutorial, but feel free to add your own.<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's also remove the random color and instead show the item's name when we hover it with the mouse.<br>
&nbsp;&nbsp;&nbsp;&nbsp;If a button has 'tooltip' set to something, it will show it when we hover the Button.<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;">    <font color="purple">def</font> append(self, item):<br><mark>        icon = Button(                                                  </mark><br>            parent = inventory.item_parent,<br>            model = <font color="green">'quad'</font>,<br><mark>            texture = item,                                             </mark><br><mark>            color = color.white,                                        </mark><br>            origin = (-.5,.5),<br>            position = self.find_free_spot(),<br>            z = -.1,<br>            )<br><mark>        name = item.replace(<font color="green">'_'</font>, <font color="green">' '</font>).title()                           </mark><br><mark>        icon.tooltip = Tooltip(name)                                    </mark><br><mark>        icon.tooltip.background.color = color.color(0,0,0,.8)           </mark><br><br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="8 Drag n' drop"/><br>
8 Drag n' drop<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;We can't move the items! That's not fun.<br>
&nbsp;&nbsp;&nbsp;&nbsp;However, that's easy to change. Just spawn a Draggable instead of Button.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Draggable inherits from Button, so the tooltips will still work!<br>
<br>
<div class="code_block" style="margin-left: 2em;font-size: 16.0px;">    <font color="purple">def</font> append(self, item):<br><mark>        icon = Draggable(                                                   </mark><br>            parent = inventory.item_parent,<br>            model = <font color="green">'quad'</font>,<br>            texture = item,<br>            color = color.white,<br>            origin = (-.5,.5),<br>            position = self.find_free_spot(),<br>            z = -.1,<br>            )<br>        name = item.replace(<font color="green">'_'</font>, <font color="green">' '</font>).title()<br>        icon.tooltip = Tooltip(name)<br>        icon.tooltip.background.color = color.color(0,0,0,.8)<br><br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="9 Snap to grid on_drop"/><br>
9 Snap to grid on_drop<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;The items can be moved now, but they don't follow the grid.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Let's round the position when we drop it.<br>
&nbsp;&nbsp;&nbsp;&nbsp;Draggable's will automatically call on_drag() and on_drop() if has them.<br>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;round the position on drop<br>
<br>
<div class="code_block" style="margin-left: 1em;font-size: 16.0px;">    <font color="purple">def</font> append(self, item):<br>        icon = Draggable(<br>            parent = inventory.item_parent,<br>            model = <font color="green">'quad'</font>,<br>            texture = item,<br>            color = color.white,<br>            origin = (-.5,.5),<br>            position = self.find_free_spot(),<br>            z = -.1,<br>            )<br>        name = item.replace(<font color="green">'_'</font>, <font color="green">' '</font>).title()<br>        icon.tooltip = Tooltip(name)<br>        icon.tooltip.background.color = color.color(0,0,0,.8)<br><br><br><mark>        <font color="purple">def</font> drop():                                                         </mark><br><mark>            icon.x = int(icon.x)                                            </mark><br><mark>            icon.y = int(icon.y)                                            </mark><br><br><br><mark>        icon.drop = drop                                                    </mark><br><br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="10 Swap items"/><br>
10 Swap items<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;Add a drag function to remember the start position as org_pos.<br>
<br>
<br>
<div class="code_block" style="margin-left: 1em;font-size: 16.0px;"><mark>        <font color="purple">def</font> drag():                                                     </mark><br><mark>            icon.org_pos = (icon.x, icon.y)                             </mark><br><br>        <font color="purple">def</font> drop():<br>            icon.x = int(icon.x)<br>            icon.y = int(icon.y)<br><br>            <font color="green"><font color="green">''</font></font>'if the spot is taken, swap positions<font color="green"><font color="green">''</font></font>'<br><mark>            for c in self.children:                                     </mark><br><mark>                if c == icon:                                           </mark><br><mark>                    continue                                            </mark><br><br><mark>                if c.x == icon.x and c.y == icon.y:                     </mark><br><mark>                    print(<font color="green">'swap positions'</font>)                             </mark><br><mark>                    c.position = icon.org_pos                           </mark><br><br><br><mark>        icon.drag = drag                                                </mark><br>        icon.drop = drop<br><br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="11 Stay inside the inventory, please"/><br>
11 Stay inside the inventory, please<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;We shouldn't be able to drop the items outside of the inventory.<br>
<br>
<div class="code_block" style="margin-left: 1em;font-size: 16.0px;">        <font color="purple">def</font> drop():<br>            icon.x = int(icon.x)<br>            icon.y = int(icon.y)<br><br><mark>            <font color="green"><font color="green">''</font></font>'if outside, return to original position<font color="green"><font color="green">''</font></font>'               </mark><br><mark>            if icon.x < 0 or icon.x >= 1 or icon.y > 0 or icon.y <= -1: </mark><br><mark>                icon.position = (icon.org_pos)                          </mark><br><mark>                return                                                  </mark><br><br>            <font color="green"><font color="green">''</font></font>'if the spot is taken, swap positions<font color="green"><font color="green">''</font></font>'<br>            for c in self.children:<br>                if c == icon:<br>                    continue<br><br>                if c.x == icon.x and c.y == icon.y:<br>                    print(<font color="green">'swap positions'</font>)<br>                    c.position = icon.org_pos<br><br>        icon.drop = drop<br><br></div><div style="font-size: 20.0px;">
<br>
<br>
<br>
<div style="font-size: 40.0px;font-weight: bold;">
<div id="12 Bugfix: Make the dragged items render on top"/><br>
12 Bugfix: Make the dragged items render on top<br>
<div style="font-size: 20.0px;font-weight: normal;">
<br>
&nbsp;&nbsp;&nbsp;&nbsp;All the items are at the same depth, so it hard to say how they will overlap.<br>
&nbsp;&nbsp;&nbsp;&nbsp;It feels natural that the item we're currently dragging stays on top,<br>
&nbsp;&nbsp;&nbsp;&nbsp;so we'll move it back a bit when we drag it and forward when we drop it.<br>
<br>
<div class="code_block" style="margin-left: 1em;font-size: 16.0px;">        <font color="purple">def</font> drag():<br>            icon.org_pos = (icon.x, icon.y)<br><mark>            icon.z -= .01   # ensure the dragged item overlaps the rest </mark><br><br>        <font color="purple">def</font> drop():<br>            icon.x = int(icon.x)<br>            icon.y = int(icon.y)<br><mark>            icon.z += .01                                                </mark><br><br>            <font color="green"><font color="green">''</font></font>'if outside, return to original position<font color="green"><font color="green">''</font></font>'<br>            if icon.x < 0 or icon.x >= 1 or icon.y > 0 or icon.y <= -1:<br>                icon.position = (icon.org_pos)<br>                return<br><br>            <font color="green"><font color="green">''</font></font>'if the spot is taken, swap positions<font color="green"><font color="green">''</font></font>'<br>            for c in self.children:<br>                if c == icon:<br>                    continue<br><br>                if c.x == icon.x and c.y == icon.y:<br>                    print(<font color="green">'swap positions'</font>)<br>                    c.position = icon.org_pos<br><br>        icon.drag = drag<br>        icon.drop = drop<br><br><br></div><div style="font-size: 20.0px;">
<br>

</html>